﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Formality.Parsing.ContextFree.LR
{
    public class ParseError : Exception
    {
        public ParseError(string Message)
            : base(Message)
        { }

        public ParseError(string Message, Exception Inner)
            : base(Message, Inner)
        { }
    }

    public class Parser<T> : IParser<T>
    {
        internal Parser(int EOFIndex, Production[] Productions, Action[,] ActionTable, int[,] GotoTable)
        {
            this.Productions = Productions;
            this.ActionTable = ActionTable;
            this.GotoTable = GotoTable;
            this.EOFIndex = EOFIndex;
        }

        public Production[] Productions { get; private set; }
        public Action[,] ActionTable { get; private set; }
        public int[,] GotoTable { get; private set; }

        #region IParser<T> Members

        public T Parse(IEnumerable<Token> Tokenizer)
        {
            // Let's make sure that there is an EOF on the end of the token stream
            Tokenizer = Tokenizer.Concat(new Token { Symbol = null, Value = "EOF" }.Singleton());

            // Init stacks
            var StateStack = new Stack<int>();
            StateStack.Push(0);

            var ValueStack = new Stack<object>();

            // Start the tokenizer
            var Token = Tokenizer.GetEnumerator();
            var EOF = !Token.MoveNext();

            while (!EOF)
            {
                var Symbol = Token.Current.Symbol;

                var a = Symbol == null ? EOFIndex : Symbol.Id;
                var s = StateStack.First();

                // a is the index of the current input symbol, s is the current state;
                // we use the action table to guide us
                var Action = ActionTable[s, a];

                if (Action.ActionType == ActionType.Shift)
                {
                    StateStack.Push(Action.Context);
                    ValueStack.Push(Token.Current.Value);

                    EOF = !Token.MoveNext();
                }
                else if (Action.ActionType == ActionType.Reduce)
                {
                    var P = Productions[Action.Context];

                    var Count = P.Symbols.Count();
                    var Arguments = new object[Count];
                    for (var i = 0; i < Count; i++)
                    {
                        StateStack.Pop();
                        Arguments[Count - i - 1] = ValueStack.Pop();
                    }

                    ValueStack.Push(GetSemanticValue(P, Arguments));
                    StateStack.Push(GotoTable[StateStack.First(), P.Head.Id]);
                }
                else if (Action.ActionType == ActionType.Accept)
                {
                    return (T)ValueStack.Single();
                }
                else
                {
                    throw new ParseError("Parse error at " + Token.Current.Value);
                }
            }

            throw new ParseError("Unexpected end-of-file");
        }

        private static object GetSemanticValue(Production P, object[] Arguments)
        {
            try
            {
                return  P.SemanticFunc.DynamicInvoke(Arguments);
            }
            catch (Exception e)
            {
                throw new ParseError("Could not invoke semantic action for production " + P, e);
            }
        }

        #endregion

        private int EOFIndex;
    }
}
